/*
 * Copyright (C) 2012 haru Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.harusource.di.mapper.impl;

import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

import android.content.Context;
import android.content.res.Resources;
import android.util.Log;

import com.harusource.di.annotation.Inject;
import com.harusource.di.core.HaruCore;
import com.harusource.di.exception.InjectException;
import com.harusource.di.exception.ViewNotFoundException;
import com.harusource.di.injector.Injector;
import com.harusource.di.mapper.AnnotaionMapper;

public class InjectAnnotationMapper implements AnnotaionMapper {
    private static final int OPTIMIZED_SIZE1 = 60;
    
    private static final int OPTIMIZED_SIZE2 = 20;
    
    private Map<Class<?>, Injector> mInjectors = null;

    private Set<Injector> mSupportSubclasses = null;

    synchronized public void setup(Set<Class<?>> classes) {
        if (classes == null || mInjectors != null || mSupportSubclasses != null){
            return;
        }

        try {
            mInjectors = new HashMap<Class<?>, Injector>(OPTIMIZED_SIZE1);
            mSupportSubclasses = new HashSet<Injector>(OPTIMIZED_SIZE2);
            for (Class<?> clazz : classes) {
                if (HaruCore.isInterfaceImpl(clazz, Injector.class)
                        && !Modifier.isAbstract(clazz.getModifiers())) {
                    Injector injector = (Injector) clazz.newInstance();
                    mInjectors.put(injector.getTargetClass(), injector);
                    if (injector.isSupportSubclass() == true){
                        mSupportSubclasses.add(injector);
                    }
                }
            }
            Log.d("TEST", Integer.toString(mInjectors.size()));
            Log.d("TEST", Integer.toString(mSupportSubclasses.size()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Class<?> getAnnotaion() {
        return Inject.class;
    }

    public Object getCache() {
        return null;
    }

    public <T1, T2> void inject(Context context, Annotation annotation, Object target, T1 _field,
            T2 cache) {
        Field field = (Field) _field;
        Class<?> clazz = field.getType();

        try {
            // none array field does not support multi-resource id
            if (((Inject) annotation).id().length > 1 && clazz.isArray() == false){
                throw new InjectException(HaruCore.ERROR_MSG6);
            }

            // find a superclass type of subclass
            for (Injector injector : mSupportSubclasses) {
                if (injector.getTargetClass().isAssignableFrom(clazz)) {
                    clazz = injector.getTargetClass();
                    break;
                }
            }

            // unsupported field
            if (mInjectors.containsKey(clazz) == false){
                throw new InjectException(HaruCore.ERROR_MSG7);
            }
            
            // create a instance through injector object 
            field.setAccessible(true);
            field.set(
                    target,
                    mInjectors.get(clazz).getInstance(context, target, (Inject) annotation,
                            field.getType().getComponentType()));
        } catch (InjectException e) {
            throw new RuntimeException(String.format(e.getMessage(), field.getName()));
        } catch (IllegalArgumentException e) {
            throw new RuntimeException(String.format(HaruCore.ERROR_MSG8, field.getName()));
        } catch (Resources.NotFoundException e) {
            if (((Inject) annotation).id()[0] == HaruCore.DEFAULT_RESOURCEID)
                throw new RuntimeException(String.format(HaruCore.ERROR_MSG5, field.getName()));
            else
                throw new RuntimeException(String.format(HaruCore.ERROR_MSG8, field.getName()));
        } catch (ViewNotFoundException e){
            throw new RuntimeException(String.format(HaruCore.ERROR_MSG8, field.getName()));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
